﻿using Google.OrTools.ConstraintSolver;
using Study.GoogleOptimizationTools.ConsoleApp.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study.GoogleOptimizationTools.ConsoleApp.Examples
{
    public class ExampleCpSolver
    {

        /// <summary>
        /// 有纸币 1, 2, 5, 10, 50, 100，从中任意可重复取出n张，凑总数为target范围内的所有解
        /// </summary>
        static void Example_1()
        {
            var source = new int[] { 1, 2, 5, 10, 50, 100 };
            var target = 50;

            //定义求解器
            var solver = new Solver("Example_1");

            //定义变量数组，表示每种币值的取出数量，长度和source一直，取值范围是0到3
            var x = solver.MakeIntVarArray(source.Length, 0, 3);

            //定义为x每个元素 乘以 每种币值，然后求和，则为凑数金额
            var total = source.Zip(x, (f, s) => f * s).Aggregate((f, s) => f + s);

            //定义约束为刚好等于target
            solver.Add(total == target);

            //定义求解过程。第一参数是变量，第二参数是选择下一个绑定变量的规则。第三参数为绑定变量赋值的规则。
            var db = solver.MakePhase(x, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);

            //执行求解。里面实际就是不断遍历变量，赋值变量，检查约束条件等等。
            solver.Solve(db);
            string split = new string('=', 10);

            //显示结果
            while (solver.NextSolution())
            {
                Console.WriteLine(split);
                foreach (var item in x.Zip(source, (f, s) => new { Count = f, Amount = s }))
                {
                    Console.WriteLine($"{item.Amount}x{item.Count.Value()}={item.Amount * item.Count.Value()}");
                }
            }
        }


        /// <summary>
        /// 类似Example_1的凑数。但目标有2个，并且需要他们的解不能重叠
        /// </summary>
        static void Example_2()
        {
            var source = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            var target1 = 3;
            var target2 = 11;


            var solver = new Solver("Example_2");

            //定义变量
            var xs1 = solver.MakeIntVarArray(source.Length, 0, 1);
            var xs2 = solver.MakeIntVarArray(source.Length, 0, 1);

            //约束
            var total1 = source.Zip(xs1, (f, s) => f * s).Aggregate((f, s) => f + s);
            solver.Add(total1 == target1);

            var total2 = source.Zip(xs2, (f, s) => f * s).Aggregate((f, s) => f + s);
            solver.Add(total2 == target2);

            //约束xs1和xs2不能重叠。就是说，两者对应元素相乘后求和必须为0
            var diff = xs1.Zip(xs2, (f, s) => f * s).Aggregate((f, s) => f + s);
            solver.Add(diff == 0);

            //求解策略
            var vars = xs1.Concat(xs2).ToArray();
            var db = solver.MakePhase(vars, Solver.CHOOSE_MIN_SIZE, Solver.ASSIGN_MIN_VALUE);

            solver.Solve(db);

            string split = new string('=', 10);
            while (solver.NextSolution())
            {
                var query = xs1.Zip(xs2, (f, s) => new { g1 = f, g2 = s })
                 .Zip(source, (gs, s) => new { x1 = gs.g1, x2 = gs.g2, amount = s });
                Console.WriteLine(split);

                foreach (var item in query)
                {
                    Console.WriteLine($"t1:{item.amount}x{item.x1.Value()}={item.amount * item.x1.Value()};t2:{item.amount}x{item.x2.Value()}={item.amount * item.x2.Value()}");
                }
            }



        }

        /// <summary>
        /// 类似Example_2的凑数。但目标有2个，并且需要他们对应的source不能重叠，并且，target1或2可以没有解
        /// </summary>
        static void Example_3()
        {
            var source = new int[] { 1, 2, 3, 4, 5 };
            var target1 = 4;
            var target2 = 7;


            var solver = new Solver("Example_3");

            //定义变量
            var xs1 = solver.MakeIntVarArray(source.Length, 0, 1);
            var xs2 = solver.MakeIntVarArray(source.Length, 0, 1);

            //约束
            var total1 = source.Zip(xs1, (f, s) => f * s).Aggregate((f, s) => f + s);
            var total2 = source.Zip(xs2, (f, s) => f * s).Aggregate((f, s) => f + s);

            //xs1有解，或无解
            var cd1 = (total1 == target1).Or(solver.MakeMax(xs1) == 0);
            var cd2 = (total2 == target2).Or(solver.MakeMax(xs2) == 0);

            //两边至少一个有解
            var cd3 = (solver.MakeMax(xs1) > 0).Or(solver.MakeMax(xs2) > 0);

            solver.Add(cd1);
            solver.Add(cd2);
            solver.Add(cd3);

            //约束xs1和xs2不能重叠。就是说，两者对应元素相乘后求和必须为0
            var other = xs1.Zip(xs2, (f, s) => f * s).Aggregate((f, s) => f + s);
            solver.Add(other == 0);

            //求解策略
            var vars = xs1.Concat(xs2).ToArray();
            var db = solver.MakePhase(vars, Solver.CHOOSE_MIN_SIZE, Solver.ASSIGN_MIN_VALUE);

            solver.Solve(db);

            string split = new string('=', 10);
            while (solver.NextSolution())
            {
                var query = xs1.Zip(xs2, (f, s) => new { g1 = f, g2 = s })
                 .Zip(source, (gs, s) => new { x1 = gs.g1, x2 = gs.g2, amount = s });
                Console.WriteLine(split);

                foreach (var item in query)
                {
                    Console.WriteLine($"t1:{item.amount}x{item.x1.Value()}={item.amount * item.x1.Value()};t2:{item.amount}x{item.x2.Value()}={item.amount * item.x2.Value()}");
                }
            }



        }

    }
}
